package cn.geminis.crypto.core.util;

import cn.geminis.core.exception.CommonException;
import cn.geminis.crypto.core.finder.AnyX509CertificateSelector;
import cn.geminis.crypto.core.finder.AnyX509CrlSelector;
import cn.geminis.crypto.core.finder.CertificateFinder;
import cn.geminis.crypto.core.x509.Crl;
import cn.geminis.crypto.core.x509.X509Certificate;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.cms.SignedData;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.*;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
 * @author Allen
 */
public class PkcsUtils {

    private PkcsUtils() {
    }

    /**
     * 编码P7B证书链格式
     *
     * @param certs 证书链证书
     * @return P7B格式证书链
     */
    public static byte[] encodeCertChain(List<X509Certificate> certs) {
        CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
        CMSProcessableByteArray msg = new CMSProcessableByteArray("".getBytes());
        for (X509Certificate cert : certs) {
            try {
                generator.addCertificate(new X509CertificateHolder(cert.getBcCertificate()));
            } catch (CMSException e) {
                throw new CommonException("构建证书链错误", e);
            }
        }
        CMSSignedData signedData = null;
        try {
            signedData = generator.generate(msg);
        } catch (CMSException e) {
            throw new CommonException("生成证书链错误", e);
        }
        try {
            return signedData.getEncoded();
        } catch (IOException e) {
            throw new CommonException("证书链编码错误", e);
        }
    }

    public static SignerInformation getSignerInformation(CMSSignedData signedData) {
        return signedData
                .getSignerInfos()
                .iterator()
                .next();
    }

    public static SignerInformation getSignerInformationFromPkcs7(byte[] pkcs7) {
        var signedData = parsePkcs7(pkcs7);
        return getSignerInformation(signedData);
    }

    public static CMSSignedData parsePkcs7(byte[] pkcs7) {
        try {
            return new CMSSignedData(new CMSAbsentContent(), pkcs7);
        } catch (CMSException e) {
            throw new CommonException("解析PKCS7错误", e);
        }
    }

    public static CMSSignedData parsePkcs7(byte[] data, byte[] pkcs7) {
        try {
            return new CMSSignedData(new CMSProcessableByteArray(data), pkcs7);
        } catch (CMSException e) {
            throw new CommonException("解析PKCS7错误", e);
        }
    }

    public static List<X509Certificate> getCertificatesFromPkcs7(byte[] pkcs7) {
        var signedData = parsePkcs7(pkcs7);
        return signedData.getCertificates()
                .getMatches(new AnyX509CertificateSelector())
                .stream()
                .map(holder -> new X509Certificate(holder.toASN1Structure()))
                .toList();
    }

    public static List<Crl> getCrlsFromPkcs7(byte[] pkcs7) {
        var signedData = parsePkcs7(pkcs7);
        return signedData.getCRLs()
                .getMatches(new AnyX509CrlSelector())
                .stream()
                .map(Crl::new)
                .toList();
    }

    public static byte[] getDataFromPkcs7(byte[] pkcs7) {
        var signedData = parsePkcs7(pkcs7);
        var sd = (SignedData) ObjectUtils.getValue(signedData, "signedData");
        var derString = (DEROctetString) sd.getEncapContentInfo().getContent();
        return Objects.isNull(derString) ? null : derString.getOctets();
    }

    public static boolean verifyPkcs7(byte[] pkcs7, byte[] data, CertificateFinder certFinder) {
        var signedData = parsePkcs7(data, pkcs7);
        SignerInformation signerInformation = getSignerInformation(signedData);
        return verifyPkcs7(signerInformation, certFinder);
    }

    public static boolean verifyPkcs7(SignerInformation signerInformation, CertificateFinder certFinder) {
        var cert = certFinder.findCert(signerInformation.getSID());
        try {
            return signerInformation.verify(ProviderUtils.getSignerInformationVerifier(cert));
        } catch (CMSException e) {
            throw new CommonException("验证PKCS7错误", e);
        }
    }

}
